﻿using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using VisualPoint.Controls.Actions;
using VisualPoint.Controls.Environments;

namespace VisualPoint
{
    public class VisualStudioEnvironment : IEnvironment
    {
        public VisualStudioEnvironment(VisualPointWindow toolWindowPane)
        {
            _toolWindowPane = toolWindowPane;

        }

        private Exception RunException(string message)
        {
            throw new RunException(message);
        }

        #region Fields

        private VisualPointWindow _toolWindowPane;
        private Rect _NormalScreen;

        private EnvDTE.Window ToolWindow
        {
            get
            {
                return Dte2.Windows.Item("{849495c3-debb-466b-b0de-7ba7d27a0b87}");

                //return Dte2.ToolWindows.GetToolWindow(_toolWindowPane.Caption) as EnvDTE.Window;
            }
        }

        private EnvDTE80.DTE2 _dte2;
        private EnvDTE80.DTE2 Dte2
        {
            get
            {
                if (_dte2 == null)
                {
                    _dte2 = _toolWindowPane.GetDte();
                    //_dte2 = GetCurrent();
                    // Get an instance of the currently running Visual Studio IDE.
                    //_dte2 = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.
                    //GetActiveObject("VisualStudio.DTE.11.0");
                }
                return _dte2;
            }
        }
        #endregion

        #region Project

        private Project GetProject(string projectName)
        {
            if (Dte2 == null
                || Dte2.Solution == null
                || Dte2.Solution.Projects == null)
            {
                return null;
            }
            try
            {
                Collection<Project> solutionFolders = new Collection<Project>();
                foreach (Project p in Dte2.Solution.Projects)
                {
                    if (p.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                    {
                        solutionFolders.Add(p);
                    }
                    else if (string.Compare(p.Name, projectName, true) == 0)
                    {
                        return p;
                    }
                }
                foreach (Project project in solutionFolders)
                {
                    Project p = FindProjects(projectName, project.ProjectItems);
                    if (p != null)
                    {
                        return p;
                    }
                }
            }
            catch
            { }
            throw RunException(string.Format(@"There is no project called ""{0}"" in the current solution.", projectName));
        }

        private static Project FindProjects(string projectName, ProjectItems projectItems)
        {
            Collection<ProjectItem> projects = new Collection<ProjectItem>();
            foreach (ProjectItem item in projectItems)
            {
                var subProject = item.SubProject;
                if (subProject == null)
                {
                    continue;
                }

                // If this is another solution folder, do a recursive call, otherwise add
                if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                {
                    projects.Add(item);
                }
                else
                {
                    if (string.Compare(subProject.Name, projectName, true) == 0)
                    {
                        return subProject;
                    }
                }
            }
            foreach (ProjectItem project in projects)
            {
                Project p = FindProjects(projectName, project.ProjectItems);
                if (p != null)
                {
                    return p;
                }
            }
            return null;
        }

        private Collection<Project> GetProjects()
        {
            Collection<Project> projects = new Collection<Project>();

            GetProjects(projects, Dte2.Solution.Projects);

            return projects;
        }

        private void GetProjects(Collection<Project> projects, Projects solutionProjects)
        {
            foreach (Project project in solutionProjects)
            {
                if (project.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                {
                    GetProjects(projects, project.ProjectItems);
                }
                else
                {
                    projects.Add(project);
                }
            }
        }

        private static void GetProjects(Collection<Project> projects, ProjectItems projectItems)
        {
            foreach (ProjectItem item in projectItems)
            {
                var subProject = item.SubProject;
                if (subProject == null)
                {
                    continue;
                }

                // If this is another solution folder, do a recursive call, otherwise add
                if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                {
                    GetProjects(projects, item.ProjectItems);
                }
                else
                {
                    projects.Add(subProject);
                }
            }
        }
        #endregion

        #region ProjectItem

        private ProjectItem GetProjectItem(Project project, string fileName)
        {
            if (project != null
                && project.ProjectItems != null
                && !string.IsNullOrEmpty(fileName))
            {
                return GetProjectItem(project.ProjectItems, fileName);
            }
            return null;
        }

        private static ProjectItem GetProjectItem(ProjectItems items, string fileName)
        {
            try
            {
                return items.Item(fileName);
            }
            catch
            { }

            foreach (ProjectItem p in items)
            {
                string name = p.Name;
                if (string.Compare(p.Name, fileName, true) == 0)
                {
                    return p;
                }
            }
            return null;
        }

        private ProjectItem GetProjectItem(Project project, string folder, string fileName)
        {
            if (string.IsNullOrWhiteSpace(folder))
            {
                return GetProjectItem(project, fileName);
            }

            folder = folder.Replace('\\', '/');
            string[] folders = folder.Split('/');

            ProjectItem p = null;
            ProjectItems items = project.ProjectItems;
            foreach (string f in folders)
            {
                p = GetProjectItem(items, f);
                if (p == null)
                {
                    return null;
                }
                items = p.ProjectItems;
            }
            return GetProjectItem(items, fileName);
        }

        #endregion

        #region Text

        public string GetSelectedText()
        {
            Document doc = GetActiveDocument();
            if (doc != null)
            {
                TextSelection ts = doc.Selection as TextSelection;
                if (ts != null)
                {
                    return ts.Text;
                }
            }
            return null;
        }


        public void SelectText(string text)
        {
            Document doc = GetActiveDocument();
            if (doc != null)
            {
                TextSelection ts = doc.Selection as TextSelection;
                if (ts != null)
                {
                    string testtext = ts.Text;
                    VirtualPoint ap = ts.ActivePoint;
                    VirtualPoint bp = ts.BottomPoint;
                    ts.SelectAll();
                    if (ts.FindText(text, (int)vsFindOptions.vsFindOptionsFromStart))
                    {
                        //ts.SelectLine();
                        //ts.Insert
                    }
                    else
                    {
                        throw RunException(string.Format(@"The text cannot be found in the document for {0}.", text));
                    }
                }
            }
        }

        public void WriteText(string Text)
        {
            Document doc = GetActiveDocument();
            if (doc != null)
            {
                TextSelection ts = doc.Selection as TextSelection;
                if (ts != null)
                {
                    ts.Text = Text;
                    //ts.Insert(Text);
                }
            }
        }

        #endregion

        #region Document

        private Document GetActiveDocument()
        {
            if (Dte2 == null)
            {
                throw RunException(@"There is no document open.");
            }
            return Dte2.ActiveDocument;
        }

        public void OpenDocument(string projectName, string folder, string fileName, string viewKind, bool selectInExplorer)
        {
            Project project = GetProject(projectName);
            if (project != null)
            {
                ProjectItem item = GetProjectItem(project, folder, fileName);
                if (item != null)
                {
                    //vsViewKindAny
                    //vsViewKindCode
                    //vsViewKindDebugging
                    //vsViewKindDesigner
                    //vsViewKindPrimary
                    //vsViewKindTextView
                    if (string.IsNullOrEmpty(viewKind))
                    {
                        viewKind = DteConstants.vsViewKindCode;
                    }

                    EnvDTE.Window w = item.Open(viewKind);
                    if (w != null)
                    {
                        w.Activate();
                    }
                    if (selectInExplorer)
                    {
                        this.SelectUIHierarchy(item, DteConstants.vsWindowKindSolutionExplorer);
                    }
                }
                else
                {
                    throw RunException(string.Format(@"There is no document for {0} {1} {2}.", project, folder, fileName));
                }
            }
        }

        public void CloseDocument(string projectName, string folder, string fileName, FileCloseOption closeOption)
        {
            if (closeOption == FileCloseOption.ActiveDocument)
            {
                Document doc = GetActiveDocument();
                if (doc != null)
                {
                    doc.Close(vsSaveChanges.vsSaveChangesNo);
                }
            }
            else if(closeOption == FileCloseOption.AllDocuments)
            {
                Dte2.Documents.CloseAll(vsSaveChanges.vsSaveChangesNo);
            }
            else
            {
                Collection<string> projectNames = new Collection<string>();
                Project project = GetProject(projectName);
                if (project != null)
                {
                    ProjectItem item = GetProjectItem(project, folder, fileName);
                    if (item != null)
                    {
                        //vsViewKindAny
                        //vsViewKindCode
                        //vsViewKindDebugging
                        //vsViewKindDesigner
                        //vsViewKindPrimary
                        //vsViewKindTextView

                        EnvDTE.Window w = item.Open(EnvDTE.Constants.vsViewKindCode);
                        if (w != null)
                        {
                            w.Close(vsSaveChanges.vsSaveChangesNo);
                        }
                    }
                    else
                    {
                        throw RunException(string.Format(@"There is no document for {0} {1} {2}.", project, folder, fileName));
                    }
                }

            }
        }

        public void CloseAllDocuments()
        {
             Dte2.Documents.CloseAll();
        }

        #endregion

        #region Windows

        public void Resize(ResizeOption resizeOption, string DockWith, vsLinkedWindowType DockAt, string Configuration)
        {
            if (resizeOption == ResizeOption.Normal)
            {
                ResizeNormal();
            }
            else if (resizeOption == ResizeOption.FullScreen)
            {
                ResizeFullScreen();
            }
            else if (resizeOption == ResizeOption.Dock)
            {
                ResizeDock(DockWith, DockAt);
            }
            else if (resizeOption == ResizeOption.Configuration)
            {
                ResizeConfiguration(Configuration);
            }
            else
            {
                ResizeSmall();
            }

        }

        private void ResizeSmall()
        {
            IVsWindowFrame frame = _toolWindowPane.Frame as IVsWindowFrame;
            if (frame != null)
            {
                frame.SetProperty((int)__VSFPROPID.VSFPROPID_FrameMode, VSFRAMEMODE.VSFM_Float);
                Guid r = Guid.Empty;
                Rect workArea = System.Windows.SystemParameters.WorkArea;
                int height = (int)workArea.Height / 2;
                int width = (int)workArea.Width / 3;
                frame.SetFramePos(VSSETFRAMEPOS.SFP_fMove, ref r, (int)(workArea.Right - width), 0, 0, 0);
                frame.SetFramePos(VSSETFRAMEPOS.SFP_fSize, ref r, 0, 0, width, height);
            }
        }

        public void ResizeNormal()
        {
            IVsWindowFrame frame = _toolWindowPane.Frame as IVsWindowFrame;
            if (frame != null)
            {
                frame.SetProperty((int)__VSFPROPID.VSFPROPID_FrameMode, VSFRAMEMODE.VSFM_Float);

                if (_NormalScreen == null)
                {
                    Guid r = Guid.Empty;
                    Rect workArea = System.Windows.SystemParameters.WorkArea;
                    int height = (int)workArea.Height / 2;
                    int width = (int)workArea.Width / 3;
                    frame.SetFramePos(VSSETFRAMEPOS.SFP_fMove, ref r, (int)(workArea.Right - width), 0, 0, 0);
                    frame.SetFramePos(VSSETFRAMEPOS.SFP_fSize, ref r, 0, 0, width, height);
                }
                else
                {
                    SetScreenSize(_NormalScreen);
                }
            }
        }

        public void ResizeFullScreen()
        {
            IVsWindowFrame frame = _toolWindowPane.Frame as IVsWindowFrame;
            if (frame != null)
            {
                frame.SetProperty((int)__VSFPROPID.VSFPROPID_FrameMode, VSFRAMEMODE.VSFM_Float);

                Guid r = Guid.Empty;

                int left;
                int top;
                int width;
                int height;
                int d;
                frame.GetFramePos(new VSSETFRAMEPOS[] { VSSETFRAMEPOS.SFP_fMove }, out r, out left, out top, out d, out d);
                frame.GetFramePos(new VSSETFRAMEPOS[] { VSSETFRAMEPOS.SFP_fSize }, out r, out d, out d, out width, out height);
                _NormalScreen = new Rect(left, top, width, height);

                SetScreenSize(System.Windows.SystemParameters.WorkArea);
            }
        }

        private void SetScreenSize(Rect rect)
        {
            IVsWindowFrame frame = _toolWindowPane.Frame as IVsWindowFrame;
            if (frame != null)
            {
                Guid r = Guid.Empty;
                frame.SetFramePos(VSSETFRAMEPOS.SFP_fMove, ref r, (int)rect.Left, (int)rect.Top, 0, 0);
                frame.SetFramePos(VSSETFRAMEPOS.SFP_fSize, ref r, 0, 0, (int)rect.Width, (int)rect.Height);
            }
        }


        public void ResizeDock(string dockWith, vsLinkedWindowType dockAt)
        {
            EnvDTE.Window window = Dte2.Windows.Item(dockWith);
            if (window == null)
            {
                throw new RunException("The dock with window must be selected");
            }

            EnvDTE.Window toolWindow = ToolWindow;
            if (toolWindow == null)
            {
                throw new RunException("The visual point window is not there");
            }

            //toolWindow.IsFloating = true;
            //toolWindow.Linkable = true;

            //window.LinkedWindowFrame.LinkedWindows.Add(toolWindow);

            Guid viewGuid = Guid.Empty;
            IVsWindowFrame windowFrame = _toolWindowPane.Frame as IVsWindowFrame;
            windowFrame.SetFramePos(VSSETFRAMEPOS.SFP_fDockRight,
               ref viewGuid, 0, 0, 0, 0);

            //EnvDTE.Window frame = Dte2.Windows.CreateLinkedWindowFrame(toolWindow, window, dockAt);

            //Dte2.MainWindow.LinkedWindows.Add(frame);
        }

        #endregion
        
        #region Tool windows

        public void SelectWindow(WindowSelectOption selectOption, string windowName, string windowConstant)
        {
            if (selectOption == WindowSelectOption.ByName)
            {
                if (string.IsNullOrWhiteSpace(windowName))
                {
                    throw new RunException("The window name should be entered for a Window select option");
                }
                EnvDTE.Window window = Dte2.ToolWindows.GetToolWindow(windowName) as EnvDTE.Window;
                if (window == null)
                {
                    throw new RunException(string.Format("The window {0} wasn't found.", windowName));
                }
                window.Activate();
            }
            else
            {
                if (string.IsNullOrWhiteSpace(windowConstant))
                {
                    throw new RunException("The standard window must be selected");
                }
                EnvDTE.Window window = Dte2.Windows.Item(windowConstant);
                if (window == null)
                {
                    throw new RunException(string.Format("The window {0} wasn't found.", windowConstant));
                }
                window.Activate();
            }
        }

        #endregion

        #region Configurations

        private IEnumerable<string> EnumerateWindowConfiguration()
        {
            IEnumerator enumerator =  Dte2.WindowConfigurations.GetEnumerator();
            while (enumerator.MoveNext())
            {
                string name;
                try
                {
                    WindowConfiguration w = enumerator.Current as WindowConfiguration;
                    name = w.Name;
                }
                catch 
                {
                    continue;
                }
                yield return name;
            }
            yield return null;
        }

        public Collection<string> GetWindowConfigurations()
        {
            Collection<string> l = new Collection<string>();

            foreach (string name in EnumerateWindowConfiguration())
            {
                l.Add(name);
            }
            return l;
        }

        public void ResizeConfiguration(string configuration)
        {
            foreach (string name in EnumerateWindowConfiguration())
            {
                WindowConfiguration w = GetWindowConfiguration(name);
                if (string.Compare(w.Name, configuration, true) == 0)
                {
                    w.Apply(true);
                    return;
                }
            }
        }

        private WindowConfiguration GetWindowConfiguration(string value)
        {
            try
            {
                return Dte2.WindowConfigurations.Item(value) as WindowConfiguration;
            }
            catch
            {
                return null;
            }
        }

        public void UpdateWindowConfiguration(string value)
        {
            WindowConfiguration c = GetWindowConfiguration(value);
            if (c != null)
            {
                c.Update();
            }
        }

        private static List<string> _DoNotDeleteWindowConfiguration = new List<string>() { "Design", "Debug", "NoToolWin" };

        public void DeleteWindowConfiguration(string value)
        {
            WindowConfiguration c = GetWindowConfiguration(value);
            if (c != null)
            {
                if (!_DoNotDeleteWindowConfiguration.Contains(value)
                    && GetActiveWindowConfiguration() != value)
                {
                    c.Delete();
                }
                else
                {
                    throw new ApplicationException(string.Format("Can not delete {0}", value));
                }
            }
        }

        public void CreateWindowConfiguration(string value)
        {
            WindowConfiguration c = GetWindowConfiguration(value);
            if (c == null)
            {
                c = Dte2.WindowConfigurations.Add(value);
            }
        }

        public string GetActiveWindowConfiguration()
        {
            return Dte2.WindowConfigurations.ActiveConfigurationName;
        }

        #endregion

        #region UIHierarchy

        private Collection<string> GetNamesFromProjectItem(ProjectItem item)
        {
            Collection<string> names = new Collection<string>();
            AddNamesFromProjectItem(item, names);
            return names;
        }

        private void AddNamesFromProjectItem(ProjectItem item, Collection<string> names)
        {
            if (item != null)
            {
                names.Insert(0, item.Name);
                AddNamesFromProjectItems(item.Collection, names);
            }
        }

        private void AddNamesFromProject(Project item, Collection<string> names)
        {
            if (item != null)
            {
                names.Insert(0, item.Name);
                string[] s = item.UniqueName.Split('\\');
                for (int i = 0; i < s.Length - 1; i++)
                {
                    names.Insert(0, s[i]);
                }
            }
        }

        private void AddNamesFromProjects(Projects items, Collection<string> names)
        {
            if (items != null)
            {
                ProjectItem item = items.Parent as ProjectItem;
                if (item != null)
                {
                    AddNamesFromProjectItem(item, names);
                }
                Project project = items.Parent as Project;
                if (project != null)
                {
                    AddNamesFromProject(project, names);
                }
            }
        }

        private void AddNamesFromProjectItems(ProjectItems items, Collection<string> names)
        {
            if (items != null)
            {
                ProjectItem item = items.Parent as ProjectItem;
                if (item != null)
                {
                    AddNamesFromProjectItem(item, names);
                }
                Project project = items.Parent as Project;
                if (project != null)
                {
                    AddNamesFromProject(project, names);
                }
            }
        }

        private void SelectUIHierarchy(ProjectItem item, string windowConstant)
        {
            Collection<string> names = GetNamesFromProjectItem(item);
            SelectUIHierarchy(names, windowConstant);
        }

        private void SelectUIHierarchy(IEnumerable<string> names, string windowConstant)
        {
            UIHierarchyItem item = FindUIHierarchy(names, windowConstant);
            if (item != null)
            {
                item.Select(vsUISelectionType.vsUISelectionTypeSelect);
            }
        }

        private UIHierarchyItem FindUIHierarchy(IEnumerable<string> names, string windowConstant)
        {
            if (string.IsNullOrWhiteSpace(windowConstant))
            {
                throw new RunException("The standard window must be selected");
            }
            EnvDTE.Window window = Dte2.Windows.Item(windowConstant);
            if (window == null)
            {
                throw new RunException(string.Format("The window {0} wasn't found.", windowConstant));
            }
            UIHierarchy uiHierarchy = window.Object as UIHierarchy;

            UIHierarchyItem item = null;
            UIHierarchyItems items = uiHierarchy.UIHierarchyItems;
            foreach (string name in names)
            {
                item = items.Item(name);
                if (item == null)
                {
                    return null;
                }
                items = item.UIHierarchyItems;
            }
            return item;
        }


        #endregion

        #region Output window

        public void WriteOutputWindow(string text)
        {
            IVsOutputWindow outWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;

            Guid generalPaneGuid = VSConstants.GUID_OutWindowGeneralPane; 

            // P.S. There's also the GUID_OutWindowDebugPane available.
            IVsOutputWindowPane generalPane;
            if (outWindow != null)
            {
                outWindow.GetPane(ref generalPaneGuid, out generalPane);
                if (generalPane != null)
                {
                    generalPane.OutputString(text);
                }
            }
        }

        #endregion
    }
}
